/**
 * 
 */
package ro.dta.idbi.patterns;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ro.dta.idbi.interfaces.IExtentGenerator;
import ro.dta.idbi.interfaces.IntegrationOperation;
import ro.dta.idbi.model.AbstractPattern;
import ro.dta.idbi.model.AutoMedUtils;
import ro.dta.idbi.model.Utils;
import uk.ac.ic.doc.automed.IntegrityException;
import uk.ac.ic.doc.automed.NotFoundException;
import uk.ac.ic.doc.automed.modelmanagement.modeldef.SQLModelDef;
import uk.ac.ic.doc.automed.reps.Schema;
import uk.ac.ic.doc.automed.reps.SchemaObject;

/**
 * <b>Mandatory column and total generalisation transformation pattern</b><br />
 * 
 * Example of use:<br />
 * 
 * <code>col_total_generalisation(&lt;&lt;table,col&gt;&gt;, &lt;&lt;newTable1&gt;&gt;,...,&lt;&lt;newTablen&gt;&gt;, &#123;val1,...,valn&#125;)</code>
 * , where: <br />
 * 
 * newTable1...newTablen represent the new names of the tables<br />
 * 
 * val1...valn represent the distinct values associated with the column
 * <i>col</i><br />
 * 
 * @author Tudor Dobrila
 * 
 */
public class ColGener extends AbstractPattern {

	/**
	 * Name of the pattern
	 */
	public static final String NAME = "Column Total Generalisation";

	/**
	 * Description of the pattern
	 */
	public static final String DESCRIPTION = "Mandatory column and total generalisation equivalence";

	/**
	 * Name of the command associated with the pattern
	 */
	public static final String COMMAND = "col_total_generalisation";

	/**
	 * Integration phase during which the command is applied
	 */
	public static final EnumSet<IntegrationOperation> OPERATION = EnumSet
			.of(IntegrationOperation.CONFORMING);

	/**
	 * Name of the column over which the transformation is applied
	 */
	private String columnName;

	/**
	 * Column that will be generalised
	 */
	private transient Map.Entry<String, String> col;

	/**
	 * Column values
	 */
	private List<String> colValues;

	/**
	 * Name of the new tables being created
	 */
	private List<String> tables;

	/**
	 * Total generalisation pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Source schema over which the transformation is applied
	 */
	public ColGener(final Schema sourceSchema) throws NotFoundException, IntegrityException {
		super(sourceSchema);

		pattern = Pattern.compile("^" + COMMAND + "[ ]*\\((" + COL_REGEX + ")" + COMMA_REGEX
				+ TABLE_SEQ_REGEX + COMMA_REGEX + VAL_SEQ_REGEX + "\\)$");
	}

	/**
	 * Total generalisation pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Name of source schema over which the transformation is applied
	 */
	public ColGener(final String sourceSchema) throws NotFoundException, IntegrityException {
		this(Schema.getSchema(sourceSchema));
	}

	/**
	 * Total generalisation pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Source schema over which the transformation is applied
	 * @param columnName
	 *            Name of the column over which the transformation is applied
	 * @param colValues
	 *            Distinct values stored in the column
	 * @param tables Name of the new tables being created
	 */
	public ColGener(final Schema sourceSchema, final String columnName,
			final List<String> colValues, final List<String> tables) throws NotFoundException,
			IntegrityException {
		this(sourceSchema);
		
		this.columnName = columnName;
		this.col = parseColName(columnName);
		this.colValues = colValues;
		this.tables = tables;
	}

	@Override
	protected List<String> getPrimitiveCommands() throws NotFoundException, IntegrityException {
		List<String> result = new ArrayList<String>();

		final String tableName = "<<" + col.getKey() + ">>";
		final SchemaObject origTable = sourceSchema.getSchemaObject(tableName);
		final String pkStr = AutoMedUtils.getPKString(sourceSchema, origTable);
		final List<String> pks = splitValues(pkStr);
		final IExtentGenerator gen = new ExtentGenerator(columnName, colValues, tables, pks, pkStr);
		StringBuilder cmd = new StringBuilder();

		// Step 1: Add new tables
		List<String> extents = gen.getExtentsForStep(1);
		for (int i = 0; i < tables.size(); i++) {
			final String crtTbl = tables.get(i);
			cmd.setLength(0);
			cmd.append("add (").append(Utils.genTableRepresentation(crtTbl));
			cmd.append(",").append(extents.get(i)).append(")");
			result.add(cmd.toString());
		}

		Object[][] pkArr = new Object[tables.size()][];
		extents = gen.getExtentsForStep(2);
		int extentNo = 0;
		// Step 2: Add PK columns to the newly created tables
		for (int i = 0; i < tables.size(); i++) {
			final String crtTbl = tables.get(i);
			pkArr[i] = new Object[pks.size() + 2];
			pkArr[i][0] = crtTbl + "_pk";
			pkArr[i][1] = crtTbl;
			int crtPos = 2;
			for (String pk : pks) {
				final SchemaObject crtPkCol = sourceSchema.getSchemaObject("<<" + col.getKey()
						+ "," + pk + ">>");
				Object[] crtPkColScheme = crtPkCol.getSchemeDefinition();
				cmd.setLength(0);
				cmd.append("add (");
				cmd.append(Utils.genColRepresentation(crtTbl, pk, crtPkColScheme[2],
						crtPkColScheme[3]));
				cmd.append(", ").append(extents.get(extentNo++)).append(")");
				pkArr[i][crtPos++] = "<<" + crtTbl + "," + pk + ">>";
				result.add(cmd.toString());
			}
		}

		// Step 3: Add PK constraint
		if (sqlModel.isFeatureInUse(SQLModelDef.PRIMARY_KEY)) {
			for (int i = 0; i < tables.size(); i++) {
				cmd.setLength(0);
				cmd.append("add (").append(Utils.genPKRepresentation(pkArr[i])).append(")");
				result.add(cmd.toString());
			}
		}

		// Step 4: Add FK constraint
		if (sqlModel.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
			for (int i = 0; i < tables.size(); i++) {
				final String crtTbl = tables.get(i);
				cmd.setLength(0);
				cmd.append("add (");
				cmd.append(Utils.genFKRepresentation(crtTbl, col.getKey(), pks));
				cmd.append(")");
				result.add(cmd.toString());
			}
		}

		// Step 5: Delete column in original table
		final String iql = gen.getExtentsForStep(5).get(0);
		cmd.setLength(0);
		cmd.append("delete (");
		cmd.append(Utils.genColRepresentation(col.getKey(), col.getValue(), "notnull", "text"));
		cmd.append(",").append(iql).append(" )");
		result.add(cmd.toString());

		return result;
	}

	@Override
	protected boolean verify() {
		// TODO Auto-generated method stub
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ro.dta.idbi.model.TransformationPattern#parse(java.lang.String)
	 */
	@Override
	protected void parse(final String trans) {
		final Matcher matcher = pattern.matcher(trans);
		matcher.find();

		parse(matcher);
	}

	/**
	 * Parse the information in the command
	 * 
	 * @param matcher
	 *            Matcher used to parse the information in the command
	 */
	public void parse(final Matcher matcher) {
		columnName = matcher.group(1);
		col = parseColName(columnName);
		colValues = splitValues(matcher.group(6));

		tables = new ArrayList<String>();
		final String[] tokens = matcher.group(2).split(",");
		for (String s1 : tokens) {
			tables.add(parseTableName(s1.trim()));
		}

		if (colValues.size() != tables.size()) {
			throw new IllegalArgumentException(
					"No. of tables to be created and no. of distinct values of the column must be the same.");
		}
	}

	/**
	 * @return Name of the column to be generalised
	 */
	public String getColumnName() {
		return columnName;
	}

	/**
	 * @return Values stored in the column to be generalised
	 */
	public List<String> getColValues() {
		return colValues;
	}

	/**
	 * @return Names of the newly created tables
	 */
	public List<String> getTables() {
		return tables;
	}

	/**
	 * Extent generator for the <i>Mandatory column and total generalisation
	 * transformation pattern</i>
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class ExtentGenerator implements IExtentGenerator {

		private transient final String columnName;
		private transient final List<String> colValues;
		private transient final List<String> tables;
		private transient final List<String> pks;
		private transient final String pkStr;

		public ExtentGenerator(final String columnName, final List<String> colValues,
				final List<String> tables, final List<String> pks, final String pkStr) {
			this.columnName = columnName;
			this.colValues = colValues;
			this.tables = tables;
			this.pks = pks;
			this.pkStr = pkStr;
		}

		@Override
		public List<String> getExtentsForStep(final int step) {
			final List<String> result = new ArrayList<String>();
			final StringBuilder iql = new StringBuilder();

			switch (step) {
			case 1:
				// Step 1: Add new tables
				for (int i = 0; i < tables.size(); i++) {
					final String value = colValues.get(i);
					iql.setLength(0);
					iql.append("[ ");
					if (pks.size() == 1) {
						iql.append("{");
					}
					iql.append(pkStr);
					if (pks.size() == 1) {
						iql.append("}");
					}
					iql.append(" | ").append("{").append(pkStr).append(",").append(value);
					iql.append("} <- ").append(columnName);
					iql.append(" ]");

					result.add(iql.toString());
				}
				break;
			case 2:
				for (int i = 0; i < tables.size(); i++) {
					final String table = tables.get(i);
					for (String pk : pks) {
						iql.setLength(0);
						iql.append("[ {").append(pkStr).append(",").append(pk).append("} | ");
						if (pks.size() == 1) {
							iql.append("{");
						}
						iql.append(pkStr);
						if (pks.size() == 1) {
							iql.append("}");
						}
						iql.append(" <- <<").append(table).append(">> ]");
						result.add(iql.toString());
					}
				}
				break;
			case 5:
				for (int i = 0; i < tables.size(); i++) {
					final String table = tables.get(i);
					final String value = colValues.get(i);
					iql.append("[ {").append(pkStr).append(", ").append(value).append("}")
							.append(" | ");
					if (pks.size() == 1) {
						iql.append("{");
					}
					iql.append(pkStr);
					if (pks.size() == 1) {
						iql.append("}");
					}
					iql.append(" <- <<").append(table).append(">> ] ++ ");
				}
				iql.setLength(iql.length() - 3);
				result.add(iql.toString());
				break;
			default:
			}

			return result;
		}

	}
}
